/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pl.tewi.jeeunit.controller;

import com.sun.appserv.security.ProgrammaticLogin;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.UserTransaction;
import org.glassfish.api.deployment.DeployCommandParameters;
import org.glassfish.api.embedded.ContainerBuilder;
import org.glassfish.api.embedded.ContainerBuilder.Type;
import org.glassfish.api.embedded.EmbeddedDeployer;
import org.glassfish.api.embedded.EmbeddedFileSystem;
import org.glassfish.api.embedded.LifecycleException;
import org.glassfish.api.embedded.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.tewi.jeeunit.config.CopyServerConfiguration;
import pl.tewi.jeeunit.config.ExtractServerConfiguration;
import pl.tewi.jeeunit.config.ServerConfigurationStrategy;
import pl.tewi.jeeunit.contexts.ApplicationServerContext;
import pl.tewi.jeeunit.contexts.FrameworkFieldInjector;
import pl.tewi.jeeunit.controllers.ServerController;
import pl.tewi.jeeunit.exceptions.ApplicationDeploymentException;
import pl.tewi.jeeunit.exceptions.EmbeddedServerException;
import pl.tewi.jeeunit.exceptions.GeneralFrameworkException;
import pl.tewi.jeeunit.exceptions.NetworkLookupException;
import pl.tewi.jeeunit.exceptions.PrepareConfigurationException;
import pl.tewi.jeeunit.types.ApplicationDesc;

/**
 * Klasa sterująca i komunikująca się z serweram aplikacyjnym Glassfish
 * @author Marcin Kwapisz
 */
public class GlassfishServerController implements ServerController {

    private final static Logger LOGGER = LoggerFactory.getLogger(GlassfishServerController.class);
    private Server server = null;
    private final ApplicationServerContext applicationServerContext;

    public GlassfishServerController(ApplicationServerContext applicationServerContext) {
        this.applicationServerContext = applicationServerContext;
    }

    /**
     * Uruchamia serwer glassfish w trybie wbudowanym
     * @throws EmbeddedServerException
     */
    @Override
    public void startEmbeddedServer() throws GeneralFrameworkException {

        LOGGER.debug(":Starting GF Server");
        Server.Builder builder = new Server.Builder("TESTOWY");
        EmbeddedFileSystem.Builder efsb = new EmbeddedFileSystem.Builder();
        efsb.autoDelete(false);
        String instanceRootPath = applicationServerContext.getProjectPath() + applicationServerContext.getInstanceParentPath() + File.separator + applicationServerContext.getInstanceName();
        File instanceDirFile = new File(instanceRootPath);
        LOGGER.debug(":Preparing GF Server configuration files");
        prepareServerConfigurationFiles(instanceRootPath);
        efsb.instanceRoot(instanceDirFile);
        EmbeddedFileSystem efs = efsb.build();
        builder.embeddedFileSystem(efs);

        server = builder.build();

        ContainerBuilder ejbContainerBuilder = server.createConfig(Type.ejb);
        server.addContainer(ejbContainerBuilder);
        //ContainerBuilder jpaContainerBuilder=server.createConfig(Type.jpa);
        //server.addContainer(jpaContainerBuilder);
        try {
            //        server.createPort(8080);
            server.start();
        } catch (LifecycleException ex) {
            LOGGER.error("", ex);
            throw new EmbeddedServerException(EmbeddedServerException.CANNOT_START, ex);
        }
    }

    /**
     * Przygotowanie plików konfiguracyjnych domeny serwera glassfish i plików schematu
     * @param instanceRootPath ścieżka do instancji serwera aplikacyjnego w której zostanie umieszczona jego konfiguracja
     * @throws PrepareConfigurationException
     */
    private void prepareServerConfigurationFiles(String instanceRootPath) throws GeneralFrameworkException {
        //określ lokalizację pliku jar bądź katalogu zawierajacego tą klasę
        //znajdują się tam pliki konfiguracji domeny i schematy dtd
        String sourcePath = GlassfishServerController.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        File sourceFile = new File(sourcePath);

        //określ czy to jest katalog czy też archiwum jar
        ServerConfigurationStrategy serverConfiguration = null;
        try {
            if (sourceFile.isDirectory()) {
                LOGGER.info("Copying server configuration files from directory");
                //konfiguracja projektu pozwala skorzystać z plików już rozpakowanych
                serverConfiguration = new CopyServerConfiguration();

            } else {
                //rozpakuj z archiwum
                LOGGER.info("Extracting server configuration files from jar");
                //wypakuj pliki znadjujące się w tych katalogach
                serverConfiguration = new ExtractServerConfiguration();
            }

            serverConfiguration.prepareConfiguration(sourceFile, "dtds", instanceRootPath + "/lib/");
            serverConfiguration.prepareConfiguration(sourceFile, "config", instanceRootPath);
        } catch (IOException ex) {
            LOGGER.error("", ex);
            throw new PrepareConfigurationException(ex);
        }
    }

    /**
     * zatrzymanie serwera aplikacyjnego glassfish
     * @throws EmbeddedServerException
     */
    @Override
    public void stopEmbeddedServer() throws GeneralFrameworkException {
        LOGGER.debug(":Stopping GF Server");
        if (server != null) {
            try {
                server.stop();
            } catch (LifecycleException ex) {
                LOGGER.error("", ex);
                throw new EmbeddedServerException(EmbeddedServerException.CANNOT_STOP, ex);
            }
        }
    }

    /**
     * Instaluje na serwerze glassfish aplikację
     * @throws ApplicationDeploymentException 
     */
    @Override
    public void deployApplication() throws GeneralFrameworkException {
        Set<ApplicationDesc> applicationDescs = applicationServerContext.getApplications();
        for (ApplicationDesc appDesc : applicationDescs) {
            LOGGER.info(":Instalacja testowanej aplikacji {}");
            File appPath = new File(applicationServerContext.getProjectPath(),appDesc.getTarget());
            EmbeddedDeployer deployer = server.getDeployer();
            DeployCommandParameters dcp = new DeployCommandParameters();
            String deployedApp = deployer.deploy(appPath, dcp);
            if (deployedApp == null) {
                LOGGER.error("Brak wsparcia dla tego typu aplikacji {}", appDesc.getName());
                throw new ApplicationDeploymentException(ApplicationDeploymentException.UNSUPPORTED_APP_TYPE);
            } else {
                LOGGER.info(":Zainstalowano aplikację: {}", deployedApp);
            }
        }
        //TODO tu mogą być problemy jeżeli aplikacji nie uda się zainstalować
    }

    @Override
    public void deployApplicationResources() {
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void configureSecuritySettings() {
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Pobiera kontekst transakcji związany z danym wątkiem
     * @return
     * @throws GeneralFrameworkException 
     */
    @Override
    public UserTransaction getCurrentTransaction() throws GeneralFrameworkException {
        try {
            UserTransaction tx=(UserTransaction) getInitialContext(null).lookup("java:comp/UserTransaction");
            return tx;
        } catch (NamingException ex) {
            throw new NetworkLookupException(NetworkLookupException.USER_TRANSACTION_LOOKUP, ex);
        }
    }

    /**
     * Wyszukuje zasób w zainstalowanych aplikacjach, lub na serwerze aplikacyjnym
     * i wstrzykuje go w pole testu opisane adnotacją
     * @param test obiekt klasy testowej
     * @param runAsUser uruchom test jako użytkownik
     * @throws GeneralFrameworkException 
     */
    @Override
    public void injectResources(Object test, String runAsUser) throws GeneralFrameworkException{
        InitialContext initialContext=getInitialContext(runAsUser);
        FrameworkFieldInjector injector= FrameworkFieldInjector.getFrameworkFieldInjector();
        injector.inject(test, initialContext, applicationServerContext);
    }

    /**
     * Pobiera kontekst do przeszukiwania JNDI serwera aplikacyjnego. 
     * @param runAsUser musi tutaj być z uwagi na różne zposoby uwierzytleniania
     * Dla niektórych serwerów aplikacyjnych odbywa się to poprzez parametry
     * przekazywane do {@link InitialContex}
     * @return InitialContext do przeszukiwania JNDI serwera aplikacyjnego
     * @throws NetworkLookupException problem z utworzeniem InitialContext
     */
    private InitialContext getInitialContext(String runAsUser) throws GeneralFrameworkException {
        try {
            Properties props = new Properties();
            props.setProperty("org.omg.CORBA.ORBInitialHost", applicationServerContext.getHostName());
            props.setProperty("org.omg.CORBA.ORBInitialPort", Integer.toString(applicationServerContext.getInstancePorts().getIiop()));
            InitialContext ctx = new InitialContext(props);
            if(runAsUser!=null && !runAsUser.isEmpty()){
                ProgrammaticLogin pl=new ProgrammaticLogin();
                pl.login(runAsUser, runAsUser);
            }
            return ctx;
        } catch (NamingException ex) {
            throw new NetworkLookupException(NetworkLookupException.INITIAL_CONTEXT_CREATION,ex);
        }
    }

    public void login(String user, String password) throws GeneralFrameworkException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void logout() throws GeneralFrameworkException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
